package com.example.demo.dao.impl;

import com.example.demo.dao.BaseDao;
import com.example.demo.domain.base.BaseEntity;
import com.example.demo.exception.BusinessException;
import com.example.demo.exception.ExceptionCode;
import com.example.demo.util.ArrayUtils;
import com.example.demo.vo.PageRequest;
import com.example.demo.vo.PageResult;
import org.hibernate.*;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate5.HibernateCallback;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;

/**
 * @author Jack
 * @Title: 基础Dao服务实现类
 * @Description: 基础Dao服务实现类
 * @date 2018年1月7日14:10:19
 */
@Repository("baseDao")
public class BaseDaoImpl extends HibernateDaoSupport implements BaseDao {
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseDaoImpl.class);

    @Resource(name = "sessionFactory")
    public void setSuperSessionFactory(SessionFactory sessionFactory) {
        super.setSessionFactory(sessionFactory);
    }

    @Resource(name = "detachedCriteriaFactory")
    private DetachedCriteriaFactory detachedCriteriaFactory;

    @Override
    @Transactional
    public <E extends BaseEntity> void save(E entity) {
        this.beforeSave(entity);
        this.getHibernateTemplate().save(entity);
    }

    private <E extends BaseEntity> void beforeSave(E entity) {
        try {
            if (entity instanceof BaseEntity) {
                BaseEntity baseEntity = entity;
                if (baseEntity.id == null) {
                    baseEntity.setCreateTime(System.currentTimeMillis());
                    baseEntity.setLastModifyTime(System.currentTimeMillis());
                } else {
                    baseEntity.setLastModifyTime(System.currentTimeMillis());
                }
                if (!entity.valid()) {
                    throw new BusinessException(ExceptionCode.INVALID_PARAMETER);
                }
            }
        } catch (Exception e) {
            LOGGER.error("BaseDao系统异常", e);
        }
    }

    @Override
    @Transactional
    public <E extends BaseEntity> void update(E entity) {
        this.beforeSave(entity);
        this.getHibernateTemplate().update(entity);
    }

    @Override
    @Transactional
    public <E extends BaseEntity> void saveOrUpdate(E entity) {
        this.beforeSave(entity);
        this.getHibernateTemplate().saveOrUpdate(entity);
    }

    @Override
    public <E extends BaseEntity> E findById(Class<E> c, Long id) {
        return this.getHibernateTemplate().get(c, id);
    }

    @Override
    public <E extends BaseEntity> E merge(E entity) {
        this.beforeSave(entity);
        return this.getHibernateTemplate().merge(entity);
    }

    @Override
    @Transactional
    public <E extends BaseEntity> void updateAndRefresh(E entity) {
        this.update(entity);
        this.getHibernateTemplate().flush();
        this.refresh(entity);
    }

    @Override
    public <E extends BaseEntity> void refresh(E entity) {
        this.getHibernateTemplate().refresh(entity);
    }

    @Override
    public void clear() {
        this.getHibernateTemplate().clear();
    }

    @Override
    public void flush() {
        this.getHibernateTemplate().flush();
    }

    @Override
    @Transactional
    public void updateByHQL(final String hql, final Object... values) {
        this.getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            @Override
            public Object doInHibernate(Session session) throws HibernateException {
                Query query = session.createQuery(hql);
                if (values != null) {
                    for (int i = 0; i < values.length; i++) {
                        query.setParameter(i, values[i]);
                    }
                }
                LOGGER.info(query.getQueryString());
                return query.executeUpdate();
            }
        });
    }

    @Override
    @Transactional
    public <E extends BaseEntity> void delete(E entity) {
        this.beforeSave(entity);
        this.getHibernateTemplate().delete(entity);
    }

    @Override
    @Transactional
    public <E extends BaseEntity> void deleteById(Class<E> clazz, long id) {
        E entity = this.findById(clazz, id);
        if (entity != null) {
            this.delete(entity);
        }
    }

    @Override
    @Transactional
    public <E extends BaseEntity> void deleteByIds(Class<E> clazz, Long[] ids) {
        for (Long id : ids) {
            this.deleteById(clazz, id);
        }
        this.flush();
    }

    @Override
    @Transactional
    public <E extends BaseEntity> int deleteAll(Class<E> clazz) {
        String hql = "delete * from " + clazz.getName();
        int count = this.getHibernateTemplate().bulkUpdate(hql);
        return count;
    }

    @Override
    @Transactional
    public void deleteBySQL(final String sql) {
        this.getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            @Override
            public Object doInHibernate(Session session) throws HibernateException {
                Query query = session.createQuery(sql);
                query.executeUpdate();
                return null;
            }
        });
    }

    @Override
    @Transactional
    public <E extends BaseEntity> void deleteByRestrictions(Class<E> clazz, Criterion... criterion) {
        final DetachedCriteria dc = detachedCriteriaFactory.buildDetachedCriteria(clazz, criterion);
        List<E> result = (List<E>) this.getHibernateTemplate().findByCriteria(dc);
        if (null != result && !result.isEmpty()) {
            this.deleteAll(result);
        }
    }

    @Override
    @Transactional
    public <E extends BaseEntity> void deleteAll(Collection<E> collection) {
        this.getHibernateTemplate().deleteAll(collection);
        this.getHibernateTemplate().flush();
    }

    @Override
    public <E extends BaseEntity> Integer getCountByRestrictions(Class<E> clazz, Criterion... criterion) {
        final DetachedCriteria dc = detachedCriteriaFactory.buildDetachedCriteria(clazz, criterion);
        return this.getCountByDetachedCriteria(dc);
    }

    private int getCountByDetachedCriteria(final DetachedCriteria detachedCriteria) {
        Object result = this.getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                // DetachedCriteria转换为Criteria
                Criteria criteria = detachedCriteria.getExecutableCriteria(session);
                Object returnObject = criteria.setProjection(Projections.rowCount()).uniqueResult();
                return returnObject;
            }
        });

        int count = result != null ? Integer.valueOf(result.toString()) : 0;
        return count;
    }

    @Override
    public <E extends BaseEntity> List<E> findByRestrictions(Class<E> clazz, Criterion... criterion) {
        final DetachedCriteria dc = detachedCriteriaFactory.buildDetachedCriteria(clazz, criterion);
        List<E> result = (List<E>) this.getHibernateTemplate().findByCriteria(dc);
        return result;
    }

    @Override
    public <E extends BaseEntity> E findUniqueByRestrictions(Class<E> clazz, Criterion... criterion) {
        DetachedCriteria dc = detachedCriteriaFactory.buildDetachedCriteria(clazz, criterion);
        return (E) findUniqueByDetachedCriteria(dc, false);
    }

    private Object findUniqueByDetachedCriteria(final DetachedCriteria detachedCriteria, final boolean isCache) {
        Object entity = this.getHibernateTemplate().executeWithNativeSession(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Criteria criteria = detachedCriteria.getExecutableCriteria(session);
                criteria.setCacheable(isCache);
                Object returnObject = criteria.uniqueResult();
                return returnObject;
            }
        });
        return entity;
    }


    @Override
    public <E extends BaseEntity> E findUniqueByRestrictions(Class<E> clazz, boolean isCache, Criterion... criterion) {
        DetachedCriteria dc = detachedCriteriaFactory.buildDetachedCriteria(clazz, criterion);
        return (E) findUniqueByDetachedCriteria(dc, isCache);
    }

    @Override
    public <E extends BaseEntity> List<E> findByIds(Class<E> clazz, Long[] ids) {
        final String hql = "from " + clazz.getName() + " c where c.id in " + ArrayUtils.toStringWithParentheses(ids);
        List<E> list = (List<E>) this.getHibernateTemplate().find(hql);
        return list;
    }

    @Override
    public <E extends BaseEntity> PageResult<E> getPage(Class<E> clazz, PageRequest pageRequest,
                                                        Criterion... criterion) {
        final DetachedCriteria listDc = detachedCriteriaFactory.buildDetachedCriteriaWithOrder(clazz, pageRequest,
                criterion);
        List<E> resultList = (List<E>) this.getHibernateTemplate().findByCriteria(listDc, pageRequest.getOffset(),
                pageRequest.getLimit());
        int totalCount = 0;
        if (pageRequest.isCountTotal()) {
            final DetachedCriteria countDc = detachedCriteriaFactory.buildDetachedCriteria(clazz, criterion);
            totalCount = getCountByDetachedCriteria(countDc);
        }
        return buildPagedResult(pageRequest, resultList, totalCount);
    }

    private <E extends BaseEntity> PageResult<E> buildPagedResult(PageRequest pageRequest, List<E> resultList, int
            totalCount) {
        return new PageResult<E>(pageRequest, resultList, totalCount);
    }
}
